package com.zwps.common.mybatis.plus.interceptor;

import lombok.AllArgsConstructor;
import lombok.Getter;

import com.baomidou.mybatisplus.core.plugins.InterceptorIgnoreHelper;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.baomidou.mybatisplus.core.toolkit.PluginUtils;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;

import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.RowConstructor;
import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.relational.EqualsTo;
import net.sf.jsqlparser.expression.operators.relational.ExpressionList;
import net.sf.jsqlparser.schema.Column;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.Statement;
import net.sf.jsqlparser.statement.delete.Delete;
import net.sf.jsqlparser.statement.insert.Insert;
import net.sf.jsqlparser.statement.select.PlainSelect;
import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.SelectBody;
import net.sf.jsqlparser.statement.select.SetOperationList;
import net.sf.jsqlparser.statement.update.Update;
import net.sf.jsqlparser.statement.values.ValuesStatement;
import java.sql.Connection;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.reflection.Reflector;

/**
 * 在update或insert sql时匹配填充表和字段进行值填充
 * 
 * @ClassName: ValueFillInterceptor
 * @Description:
 * @author lyh
 * @date 2023年5月25日 下午11:46:51
 *
 */
public class ValueFillInterceptor extends TenantLineInnerInterceptor {

    private Map<String, List<FieldValue>> INSERT = new HashMap<String, List<FieldValue>>();
    private Map<String, List<FieldValue>> UPDATE = new HashMap<String, List<FieldValue>>();

    // 为所有表注入字段值的标识
    private final String ALL_TABLE = "*";

    public void addInsertValueFill(String tableName, List<FieldValue> list) {
        INSERT.put(tableName, list);
    }

    public void addUpdateValueFill(String tableName, List<FieldValue> list) {
        UPDATE.put(tableName, list);
    }

    public void addInsertValueFill(String tableName, FieldValue fieldValue) {
        addValueFill(INSERT, tableName, fieldValue);
    }

    public void addUpdateValueFill(String tableName, FieldValue fieldValue) {
        addValueFill(UPDATE, tableName, fieldValue);
    }

    public void addValueFill(Map<String, List<FieldValue>> map, String tableName, FieldValue fieldValue) {
        List<FieldValue> list = map.get(tableName);
        if (list == null) {
            map.put(tableName, list = new ArrayList<FieldValue>());
        }
        list.add(fieldValue);
    }

    public void addAllTableInsertValueFill(List<FieldValue> list) {
        INSERT.put(ALL_TABLE, list);
    }

    public void addAllTableUpdateValueFill(List<FieldValue> list) {
        UPDATE.put(ALL_TABLE, list);
    }

    @Override
    protected void processDelete(Delete delete, int index, String sql, Object obj) {
    }

    @Override
    protected void processSelect(Select select, int index, String sql, Object obj) {
    }

    @Getter
    @AllArgsConstructor
    public static class FieldValue {
        private String fieldName;
        private Function<String, Expression> value;
    }

    private List<FieldValue> checkTable(Map<String, List<FieldValue>> fillTableMap, String tableName) {
        List<FieldValue> fieldValueList = fillTableMap.get(tableName);
        if (fieldValueList == null || fieldValueList.isEmpty()) {
            return null;
        }
        return fieldValueList;
    }

    protected void processUpdateOrInsert(Statement st, Table table, List<Column> columns,
            Map<String, List<FieldValue>> fillTableMap, Object reflector) {
        List<FieldValue> fieldValueList = checkTable(fillTableMap, table.getName());

        // 是否所有表都需要注入
        boolean allTable = false;
        if (fieldValueList == null) {
            if (allTable = (fieldValueList = checkTable(fillTableMap, ALL_TABLE)) == null) {
                // 过滤退出执行
                return;
            }
            allTable = true;
        }
        for (FieldValue fieldValue : fieldValueList) {
            boolean needFill = true;
            String fieldName = fieldValue.getFieldName();
            for (Column column : columns) {
                if (column.getColumnName().equalsIgnoreCase(fieldName)) {
                    needFill = false;
                    break;
                }
            }
            if (needFill) {
                if (allTable) {
                    // 处理所有表都需要注入的逻辑，前提是DO类中存在对应的字段，不存在的就跳过
                    if (reflector instanceof Reflector) {
                        String name = ((Reflector) reflector).findPropertyName(fieldName.replace("_", ""));
                        if (name == null) {
                            continue;
                        }
                    }
                }

                Expression value = fieldValue.getValue().apply(fieldName);
                if (value != null) {
                    if (st instanceof Insert) {
                        processInsertFill((Insert) st, null, columns, fieldName, value);
                    } else if (st instanceof Update) {
                        EqualsTo equalsTo = new EqualsTo();
                        equalsTo.setLeftExpression(getAliasColumn(table, fieldName));
                        equalsTo.setRightExpression(value);
                        ((Update) st).addExpressions(equalsTo);
                    }
                }
            }
        }
    }

    @Override
    protected void processUpdate(Update update, int index, String sql, Object obj) {
        processUpdateOrInsert(update, update.getTable(), update.getColumns(), UPDATE, obj);
    }

    @Override
    protected void processInsert(Insert insert, int index, String sql, Object obj) {
        processUpdateOrInsert(insert, insert.getTable(), insert.getColumns(), INSERT, obj);
    }

    protected Column getAliasColumn(Table table, String columnName) {
        StringBuilder column = new StringBuilder();
        // 禁止 `为了兼容隐式内连接，没有别名时条件就需要加上表名`
        // 该起别名就要起别名
        if (table.getAlias() != null) {
            column.append(table.getAlias().getName());
        } else {
            column.append(table.getName());
        }
        column.append(StringPool.DOT).append(columnName);
        return new Column(column.toString());
    }

    @Override
    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {
        PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);
        MappedStatement ms = mpSh.mappedStatement();
        SqlCommandType sct = ms.getSqlCommandType();
        if (sct == SqlCommandType.INSERT || sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {
            if (InterceptorIgnoreHelper.willIgnoreTenantLine(ms.getId())) {
                return;
            }
            PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();
            Reflector reflector = mpSh.configuration().getReflectorFactory()
                    .findForClass(mpBs.parameterObject().getClass());
            mpBs.sql(parserMulti(mpBs.sql(), reflector));
        }
    }

    private void processInsertFill(Insert insert, Object obj, List<Column> columns, String fieldName,
            Expression value) {
        columns.add(new Column(fieldName));
        // fixed gitee pulls/141 duplicate update
        List<Expression> duplicateUpdateColumns = insert.getDuplicateUpdateExpressionList();
        if (CollectionUtils.isNotEmpty(duplicateUpdateColumns)) {
            EqualsTo equalsTo = new EqualsTo();
            equalsTo.setLeftExpression(new StringValue(fieldName));
            equalsTo.setRightExpression(value);
            duplicateUpdateColumns.add(equalsTo);
        }

        Select select = insert.getSelect();
        if (select != null) {
            SelectBody selectBody = select.getSelectBody();
            if (selectBody instanceof PlainSelect) {
                this.processInsertSelect(selectBody, (String) obj);
            } else if (selectBody instanceof SetOperationList) {
                this.processInsertSet((SetOperationList) selectBody, value);
            }
        }
//        else if (insert.getItemsList() != null) {
//            // fixed github pull/295
//            ItemsList itemsList = insert.getItemsList();
//            if (itemsList instanceof MultiExpressionList) {
//                ((MultiExpressionList) itemsList).getExpressionLists().forEach(el -> el.getExpressions().add(value));
//            } else {
//                ((ExpressionList) itemsList).getExpressions().add(value);
//            }
//        } else {
//            throw ExceptionUtils
//                    .mpe("Failed to process multiple-table update, please exclude the tableName or statementId");
//        }
    }

    private void processInsertSet(SetOperationList operationList, Expression value) {
        List<SelectBody> selectBodyList = operationList.getSelects();
        if (CollectionUtils.isNotEmpty(selectBodyList)) {
            for (SelectBody selectBody : selectBodyList) {
                ValuesStatement valuesStatement = (ValuesStatement) selectBody;
                ExpressionList expressionList = (ExpressionList) valuesStatement.getExpressions();
                for (Expression expression : expressionList.getExpressions()) {
                    RowConstructor rowConstructor = (RowConstructor) expression;
                    rowConstructor.getExprList().addExpressions(value);
                }
            }
        }
    }
}
